/**
 * @file simple_message_client_1186.c
 *
 * TCP/IP Simple Message Board - Client
 *
 * This ist the simple_message_client, which is inspired by the reference
 * implementation. It sends a message to the simple_message_server and takes
 * it's response.
 *
 * This client supports IPv4 sockets as well as IPv6 sockets, wrapped with
 * buffered streams for easy data sending.
 *
 * @author Robert Grasböck <rober.grasboeck@technikum-wien.at>
 * @author David Schreiber <david.schreiber@technikum-wien.at>

 * @date 2010/12/04
 * @version $Revision: 26 $
 *
 * @todo Code Revisioning - Final cleanup
 * @todo fscanf() in use; ask if it is not allowed! (C-Richtlinien)
 */

/*#include <errno.h>*/
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <netinet/in.h>
/*#include <stropts.h>*/

/* VERBOSE pre processor macro */
#define VERBOSE(format, ...)    \
do { if(cfg.verbose == 1) { \
        printf("%s [%s, %s(), line: %d]: ", cfg.applicationName, __FILE__, __FUNCTION__, __LINE__); \
        printf(format, ## __VA_ARGS__); } \
} while (0)

/* ERROR pre processor macro */
#define ERROR(format, ...)    \
do { \
    fprintf(stderr, "%s: ", cfg.applicationName); \
    fprintf(stderr, format, ## __VA_ARGS__);  \
    exit(1); \
} while (0)

/* Chunk size used for file writing */
#define BUFFER_SIZE 256
#define CHUNK_SIZE BUFFER_SIZE


/**
 * This struct is for easily passing the application options given by command line agruments
 */
struct applicationOptions {
        char*   applicationName;    /* Application name (execution name) */
	char* 	server;             /* Server name */
	char* 	port;               /* Server port */
	char* 	user;               /* User name */
	char* 	image;              /* Optional image URL */
	char* 	message;            /* Message to send */
	int     help;               /* Print help/usage message */
        int     verbose;            /* Verbose mode */
};

/**
 * GLOBALS
 */
/* Global configuration */
static struct applicationOptions cfg = { NULL, NULL, NULL, NULL, NULL, NULL, 0, 0 };
          
/**
 * This function prints the usage message / help
 */
void print_usage(void) {
	printf("usage: %s options\n", cfg.applicationName);
	printf("options:\n");
	printf("\t-s, --server <server>\tfull qualified domain name or IP address of the server\n");
	printf("\t-p, --port <port>\twell-known port of the server [0..65535]\n");
        printf("\t-u, --user <name>\tname of the posting user\n");
        printf("\t-i, --image <URL>\tURL pointing to an image of the posting user\n");
        printf("\t-m, --message <message> message to be added to the bulletin board\n");
        printf("\t-v, --verbose\t\tverbose output\n");
        printf("\t-h, --help\n");
}

/**
 * This function parses the command line arguments given with execution
 * and saves them inside the application_options structure
 *
 * @param [in] argc		argument count
 * @param [in] argv		arguments
 */
void parse_arguments(int argc, char* argv[]) {
    int c = 0;              /* The current option */
    int optionIndex = 0;    /* The index of currently parsed option */
    int noparam = 1;        /* Used to check if no option was passed at all (short and long) */
    static struct option longopts[] = {
        { "server", required_argument,  NULL, 's' },
        { "port",   required_argument,  NULL, 'p' },
        { "user",   required_argument,  NULL, 'u' },
        { "image",  required_argument,  NULL, 'i' },
        { "message",required_argument,  NULL, 'm' },
        { "verbose",no_argument,        NULL, 'v' },
        { "help",   no_argument,        NULL, 'h' }
    };

    /* Parse all command line options */
    while((c = getopt_long(argc, argv, "s:p:u:i:m:vh", longopts, &optionIndex)) != -1) {
        /* Set this to zero after first option found */
        noparam = 0;
        /* Do configuration regarding to option */
        switch(c) {
            case 's': cfg.server = optarg; break;
            case 'p': cfg.port = optarg; break;
            case 'u': cfg.user = optarg; break;
            case 'i': cfg.image = optarg; break;
            case 'm': cfg.message = optarg; break;
            case 'v': cfg.verbose = 1; break;
            case 'h': cfg.help = 1; break;
            default:  cfg.help = 1; break;
        }
    }

    /* If no option was passed (while-loop never entered),
       or more arguments than options where given, display the usage message */
    if(noparam == 1 || optind < argc) cfg.help = 1;
}

/**
 * This function constructs the message sent to the server
 *
 * @param [out] message_buffer an existing string variable to pass the message back
 * @param [out] message_length size of the constructed message
 */
void construct_message(char** message_buffer, int* message_length) {
    /* Construct message with or without image URL? */
    if(cfg.image != NULL) {
        /* Calculate length of message including image */
        *message_length = strlen(cfg.user) + strlen(cfg.image) + strlen(cfg.message) + 12;

        /* Allocate memory needed to store message */
        if((*message_buffer = malloc(*message_length)) == NULL)
            ERROR("Could not allocate memory for message\n");

        /* Construct the message */
        sprintf(*message_buffer, "user=%s\nimg=%s\n%s\n", cfg.user, cfg.image, cfg.message);
    } else {
        /* Calculate length of message without image */
        *message_length = strlen(cfg.user) + strlen(cfg.message) + 7;

        /* Allocate memory needed to store message */
        if((*message_buffer = malloc(*message_length)) == NULL)
            ERROR("Could not allocate memory for message\n");

        /* Construct the message */
        sprintf(*message_buffer, "user=%s\n%s\n", cfg.user, cfg.message);
    }
}

/**
 * This function handles the sending part of the client. It sends a message
 * over an already opened buffered stream. After sending the message memory
 * is deallocated.
 *
 * @param [in] fd_socket    - The TCP socket used for sending; Must be in connected mode.
 * @param [in] message      - The message to send
 * @param [in] len          - The message size
 */
void send_message(FILE *fs_send, char *message, int len) {
    VERBOSE("Send message (len=%d) %s", len, message);
    /* Try writing the message to the server */

    if(fwrite(message, len, 1, fs_send) != 1)
        ERROR("Cannot write to socket\n");

    VERBOSE("Sent request user=\"%s\", img_url=\"%s\", message=\"%s\" to server %s\n", cfg.user, cfg.image, cfg.message, cfg.server);

    /* Free the message memory since it is not used anymore*/
    free(message);

    VERBOSE("Message memory deallocated\n");
}

/**
 * This function creates an IPv4 socket based on the server info
 *
 * @param server_info   - The pre fetched server info of getaddrinfo() call
 * @return int          - Created IPv4 socket
 */
int create_ipv4_socket(struct addrinfo *server_info) {
    int fd_socket = 0;                  /* IPv4 socket handle */
    struct sockaddr_in* socket_addr_ip4;/* IPv4 socket address */
    char ip4_addr[INET_ADDRSTRLEN];     /* Representing the socket IP address */
    unsigned short int ip4_port;        /* Representing the socket port */

    /* Cast the generic socket address into IPv4 format*/
    socket_addr_ip4 = (struct sockaddr_in*) server_info->ai_addr;

    /* Create the human readable IP string */
    inet_ntop(AF_INET, &(socket_addr_ip4)->sin_addr, ip4_addr, INET_ADDRSTRLEN);

    /* Create the human readable Port */
    ip4_port = htons(socket_addr_ip4->sin_port);

    VERBOSE("Obtained IPv4 address %s, port number %hu for server %s and port %s\n", ip4_addr, ip4_port, cfg.server, cfg.port);

    /* Create the new socket */
    fd_socket = socket(AF_INET, server_info->ai_socktype, server_info->ai_protocol);

    /* Did it work? */
    if(fd_socket == -1) ERROR("Cannot open socket\n");

    VERBOSE("Created IPv4 SOCK_STREAM socket\n");

    return fd_socket;
}

/**
 * This function creates an IPv6 socket based on the server info
 *
 * @param [in] server_info - The pre fetched server info of getaddrinfo() call
 * @return int - Created IPv6 socket
 */
int create_ipv6_socket(struct addrinfo *server_info) {
    int fd_socket = 0;                  /* IPv6 socket handle */
    struct sockaddr_in6* socket_addr_ip6;/* IPv6 socket address */
    char ip6_addr[INET6_ADDRSTRLEN];     /* Representing the socket IP address */
    unsigned short int ip6_port;        /* Representing the socket port */

    /* Cast the generic socket address into IPv4 format*/
    socket_addr_ip6 = (struct sockaddr_in6*) server_info->ai_addr;

    /* Create the human readable IP string */
    inet_ntop(AF_INET6, &(socket_addr_ip6)->sin6_addr, ip6_addr, INET6_ADDRSTRLEN);

    /* Create the human readable Port */
    ip6_port = htons(socket_addr_ip6->sin6_port);

    VERBOSE("Obtained IPv6 address %s, port number %hu for server %s and port %s\n", ip6_addr, ip6_port, cfg.server, cfg.port);

    /* Create the new socket */
    fd_socket = socket(AF_INET6, server_info->ai_socktype, server_info->ai_protocol);

    /* Did it work? */
    if(fd_socket == -1) ERROR("Cannot open socket\n");

    VERBOSE("Created IPv6 SOCK_STREAM socket\n");

    return fd_socket;
}

/**
 * This function creates an IPv4 or IPv6 TCP socket based on the system config
 *
 * @param [out] fd_socket   - The created TCP socket
 * @param [out] server_addr - The server information fetched
 */
void create_socket(int *fd_socket, struct addrinfo **server_addr) {
    struct addrinfo server_hints;   /* Socket hinting */

    /* Set well defined values (0) */
    memset(&server_hints, 0, sizeof server_hints);

    /* Don't care for IPv4 or IPv6 */
    server_hints.ai_family = AF_UNSPEC;

    /* Only use TCP stream socket */
    server_hints.ai_socktype = SOCK_STREAM;

    /* Only use protocols used by client machine. For example
     * don't use IPv6 if there is no IPv6 address configured locally */
    server_hints.ai_flags = AI_ADDRCONFIG;          

    VERBOSE("Resolving host \"%s\" ...\n", cfg.server);

    /* Resolve server based on hinting, allocates sizeof (struct addrinfo) */
    if(getaddrinfo(cfg.server, cfg.port, &server_hints, server_addr) != 0)
        ERROR("Could not resolve name for given server\n");

    /* Select protocol family */
    switch((*server_addr)->ai_family) {
        case AF_INET:
            VERBOSE("Using protocol family IPv4 ...\n");
            /* Setup IPv4 socket */
            *fd_socket = create_ipv4_socket(*server_addr);
            break;
        case AF_INET6:
            VERBOSE("Using protocol family IPv6 ...\n");
            /* Setup IPv6 socket */
            *fd_socket = create_ipv6_socket(*server_addr);
            break;
        default:
            ERROR("Unsupported protocol family encountered\n");
    }
}

/**
 * This function creates two buffered file streams wrapped around a TCP socket,
 * one for sending and one for receiving. 
 *
 * @param [in]  fd_socket  - The socket to be wrapped
 * @param [out] fs_send    - The created handle for sending
 * @param [out] fs_receive - The created handle for receiving
 */
void create_streams(int fd_socket, FILE **fs_send, FILE **fs_receive) {
    VERBOSE("Creating sending stream ...\n");

    *fs_send = fdopen(fd_socket, "w");
    if(*fs_send == NULL) ERROR("Could not open sending stream");

    VERBOSE("Creating receiving stream ...\n");

    *fs_receive = fdopen(fd_socket, "r");
    if(*fs_receive == NULL) ERROR("Could not open receiving stream");
}

/**
 * This function opens the TCP connection to the server
 *
 * @param [in] fd_socket   - The socket which should be used for connection
 * @param [in] server_info - The server socket to connect to
 */
void begin_connect(int fd_socket, struct addrinfo *server_info) {
    VERBOSE("Opening connection to server ...\n");

    /* Try connecting to the server */
    if(connect(fd_socket, server_info->ai_addr, server_info->ai_addrlen) < 0)
        ERROR("Cannot connect to server - Connection refused\n");

    VERBOSE("Connected to port %s of server %s\n", cfg.port, cfg.server);

    /* Free server_info - not needed anymore (allocated in getaddrinfo) */
    freeaddrinfo(server_info);
}

/**
 * This function is used for saving a file to the file system, while reading
 * it from a file stream.
 *
 * @param [in] fs_receive   - The file stream to read from
 * @param [in] file_name    - Name of the file
 * @param [in] file_len     - Length of the file
 */
void transfer_file(FILE *fs_receive, char* file_name, int file_len) {
    FILE *fp;               /* File handle for local file */
    char buffer[CHUNK_SIZE];
    unsigned int chunks = file_len / CHUNK_SIZE;         /* Number of chunks to write */
    unsigned int last_chunk = file_len % CHUNK_SIZE;     /* Size of last chunk */
    unsigned int i;
    
    VERBOSE("Opening file \"%s\" for writing of %d bytes in %d chunks @%d bytes and a last remainder chunk @%d bytes ...\n", file_name, file_len, chunks, CHUNK_SIZE, last_chunk);

    /* Try opening the file in binary write mode */
    if((fp = fopen(file_name, "wb")) == NULL) ERROR("Cannot open file for writing\n");

    VERBOSE("Opened file \"%s\" for writing of %d bytes in %d chunks @%d bytes and a last remainder chunk @%d bytes ...\n", file_name, file_len, chunks, CHUNK_SIZE, last_chunk);

    /* Copy all full chunks to file */
    for(i = 0; i<chunks; i++) {
        if(fread(buffer, 1, CHUNK_SIZE, fs_receive) < CHUNK_SIZE)
            ERROR("Could not read file from socket stream (server closed connection?)\n");
        
        if(fwrite(buffer, 1, CHUNK_SIZE, fp) < CHUNK_SIZE || fflush(fp) == EOF)
            ERROR("Error while writing data to the file\n");

        VERBOSE("Copied chunk %d @%d bytes\n", i, CHUNK_SIZE);
    }

    /* Copy the remainder chunk to file */
    if(fread(buffer, 1, last_chunk, fs_receive) < last_chunk)
        ERROR("Could not read last chunk from socket stream\n");
    if(fwrite(buffer, 1, last_chunk, fp) < last_chunk || fflush(fp) == EOF)
        ERROR("Error while writing last chunk to the file\n");

    VERBOSE("Copied chunk %d @%d bytes\n", i, last_chunk);

    /* Close the file stream - and eventually flush remaining data */
    if(fclose(fp) == EOF) ERROR("Error while finishing writing the file\n");

    VERBOSE("Closed file \"%s\"\n", file_name);
}


#ifdef RECEIVE_OLD

/**
 * This function receives and parses messages send by the server. After
 * evaluating the server's status code it parses one file send after another,
 * and calls the file transfer routine.
 *
 * @param [in] fs_receive   - Stream to read from
 */
void begin_receiving(FILE *fs_receive) {
    char *buffer;                   /* Used for buffered receiving */
    char *buffer_position = NULL;   /* Pointer needed for buffer resizing */
    int buffer_size = BUFFER_SIZE;  /* Holding the actual buffer size */
    int status_code = 0;            /* Server status code received */
    int file_count = 0;             /* Number of files received */
    char file_name[1024];           /* File name of current file */
    int file_len;                   /* Length of current file */
    
    /* Initialize buffer*/
    if((buffer = malloc(buffer_size)) == NULL)
        ERROR("Could not allocate buffer memory.\n");

    VERBOSE("Receiving status code ...\n");

    /* Read the server status code (till \n) */
    fgets(buffer, buffer_size, fs_receive);

    /* Parse the status code */
    if(sscanf(buffer, "status=%d\n", &status_code) <= 0)
        ERROR("Malformed status code received.\n");

    VERBOSE("Received status code %d\n", status_code);

    /* Prepare our buffer pointer */
    buffer_position = buffer;

    /* Read file information */
    while(fgets(buffer_position, BUFFER_SIZE, fs_receive) != NULL) {
        /* Do we need to resize our buffer (newline missing)? */
        if(strchr(buffer, '\n') == NULL) {
            /* Save the new size */
            buffer_size += BUFFER_SIZE;

            /* Reallocate the memory */
            if((buffer = realloc(buffer, buffer_size)) == NULL)
                ERROR("Could not reallocate buffer memory.\n");
            
            /* Move the pointer */
            buffer_position = buffer + buffer_size - BUFFER_SIZE - 1;

            /* Read the data into the buffer */
            continue;
        }

        /* Determine what file information we got */
        if(strstr(buffer, "file=") != NULL) {
            /* Parse file name */
            if(sscanf(buffer, "file=%s\n", file_name) <= 0) ERROR("Malformed file name received.\n");
        } else if(strstr(buffer, "len=") != NULL) {
            /* Parse file length*/
            if(sscanf(buffer, "len=%d\n", &file_len) <= 0) ERROR("Malformed file length received.\n");
            
            VERBOSE("Received file information for file #%d (filename: \"%s\", length: %d)\n", file_count, file_name, file_len);

            /* Call the file transfer routinge*/
            transfer_file(fs_receive, file_name, file_len);

            /* Count the file as transferred */
            file_count++;
        } else ERROR("Malformed file information received.\n");

        /* Shrink the buffer */
        buffer_size = BUFFER_SIZE;
        
        /* Reallocate the memory */
        if((buffer = realloc(buffer, buffer_size)) == NULL)
            ERROR("Could not reallocate buffer memory.\n");
        
        /* Reset the buffer pointer */
        buffer_position = buffer;
    }

    VERBOSE("EOF received. Stopped reading.\n");

    /* Free buffer memory */
    free(buffer);
}

#else

/// different receive states, for small c statemachine
typedef enum {
    GET_STATUS, ///< get status value state
    GET_NAME,   ///< get file name state 
    GET_LEN     ///< get file lenght state
} Receive_State;

void begin_receiving(FILE *fs_receive) {
    
    // format =  status=xxxxxxxxxx\nfile=nameabc\nlen=1234567890\n_DATA_WITH_LEN_\nfile=namedef\nled=1234567890\n_DATA_WITH_LEN_
    // what we need to get:
    // -> check for valid parameter
    // -> statuscode, filename, len, filecount
    // ->
    char *buffer = NULL;                /* Used for buffered receiving */
    int offset = 0;                     /* offset needed for reading into buffer */

    int status_code = 0;                /* Server status code received */
    char *file_name = NULL;             /* File name of current file */
    int file_len;                       /* Length of current file */
    
    int file_count = 0;                 /* Number of files received */

    Receive_State state = GET_STATUS;   /* current receive state */
    
    /* Initialize buffer */
    if((buffer = malloc(BUFFER_SIZE)) == NULL)
        ERROR("Could not allocate buffer memory.\n");    
    
    /* Read file information (NOTE: adds a \0 to the end !!!) */
    while(fgets(buffer + offset, BUFFER_SIZE, fs_receive) != NULL) {

        /* Do we need to resize our buffer? (-> newline is missing) */
        if(strchr(buffer, '\n') == NULL) {
            /* Move offset after last written byte */
            offset += BUFFER_SIZE;
            
            /* Reallocate the memory, already received stuff is still available */
            if((buffer = realloc(buffer, BUFFER_SIZE + offset)) == NULL)
                ERROR("Could not reallocate buffer memory.\n");
            
            /* Go on with reading into the buffer */
            continue; 
        }

        switch (state) {
            case GET_STATUS:
                /* Parse the status code */
                if(sscanf(buffer, "status=%d\n", &status_code) <= 0)
                    ERROR("Malformed status code received.\n");
                
                VERBOSE("Received status code %d\n", status_code);

                /* Next state: Receive file name */
                state = GET_NAME; 
                break;
                
            case GET_NAME:
                /* Allocate memory for filename */
                if ((file_name = malloc(BUFFER_SIZE + offset)) == NULL)
                    ERROR("Could not allocate filename memory.\n");

                /* Parse the file name */
                if(sscanf(buffer, "file=%s\n", file_name) <= 0)
                    ERROR("Malformed file name received.\nThis is not a ");

                /* Next state: Get file length */
                state = GET_LEN;
                break;
                
            case GET_LEN:
                if(sscanf(buffer, "len=%d\n", &file_len) <= 0) 
                    ERROR("Malformed file length received.\n");

                VERBOSE("Received file information for file #%d (filename: \"%s\", length: %d)\n", file_count, file_name, file_len);
                
                /* Call the file transfer routine */
                transfer_file(fs_receive, file_name, file_len);

                /* Delete old filename memory */
                free(file_name); 

                /* Increase number of files transferred */
                file_count++; 

                /* Next state: Receive file name */
                state = GET_NAME; 
                
                break;
        }
                    
        /* Return buffer offset to the beginning */
        offset = 0;

        /* Resize buffer to standard size */
        if((buffer = realloc(buffer, BUFFER_SIZE)) == NULL)
            ERROR("Could not reallocate buffer memory.\n");
                    
    } // while loop
    
    free(buffer);
}

#endif

/**
 * This function closes a stream wrapped around a tcp socket. It behaves
 * similar as fclose() but does not close the underlying socket completely,
 * allowing further usage of the socket.
 *
 * @param [in] fs_stream - The stream to close
 * @param [in] direction - SHUT_WR, SHUT_RD, ... commands for shutdown()
 */
void shutdown_stream(FILE *fs_stream, int direction) {
    VERBOSE("Trying to flush file pointer associated with socket ...\n");

    /* Flush the stream to ensure everything was transmitted */
    fflush(fs_stream);

    VERBOSE("Trying to shutdown socket asociated with file pointer ...\n");

    /* Shutdown the socket which lies under the stream */
    if(shutdown(fileno(fs_stream), direction) == -1) ERROR("Could not shutdown socket\n");

    VERBOSE("Trying to close file pointer asociated with socket ...\n");

    /* Free memory of stream */
    free(fs_stream);

    VERBOSE("Shutdown complete ...\n");
}
/**
 * This function contains the network communication code to the server
 *
 * @param [in] cfg the client configuration containing everything needed.
 */
void start_communication(void) {

    int fd_socket = 0;              /* The client's local TCP socket */
    FILE *fs_receive, *fs_send;     /* File streams used for file based TCP send and receive */
    struct addrinfo *server_info;   /* The server's socket information */
    char *message;                  /* The buffer holding the message to send */
    int messagesize;                /* Size of the Message to send */


    VERBOSE("Going to create socket ...\n");

    /* Create the new socket */
    create_socket(&fd_socket, &server_info);

    VERBOSE("Going to create streams for both directions ...\n");

    /* Create reading and sending file streams */
    create_streams(fd_socket, &fs_send, &fs_receive);

    VERBOSE("Going to connect ...\n");
    
    /* Connect to the server */
    begin_connect(fd_socket, server_info);

    VERBOSE("Going to construct message ...\n");
    
    /* Construct the message to send */
    construct_message(&message, &messagesize);

    VERBOSE("Going to send the following message consisting of %d bytes ...\n%s", messagesize, message);

    /* Send the message to the server */
    send_message(fs_send, message, messagesize);

    VERBOSE("Going to shut down the sending stream ...\n");

    /* Call shutdown for sending stream - fs_send will be freed */
    shutdown_stream(fs_send, SHUT_WR);

    VERBOSE("Switching to receive mode ...\n");

    /* Receive the response and length */
    begin_receiving(fs_receive);

    VERBOSE("Everything received. Server closed connection\n");

    VERBOSE("Closing Stream ...\n");
    
    free(fs_receive);
    
    VERBOSE("Going to close socket ...\n");

    close(fd_socket);

    VERBOSE("Closed socket. Everything finished!\n");
}

/**
 * Main function of the application.
 */
int main(int argc, char* argv[]) {
    /* Get application name for usage print */
    cfg.applicationName = *argv;

    VERBOSE("Parsing command line arguments for options ...\n");
    
    /* Parse command line arguments */
    parse_arguments(argc, argv);  

    VERBOSE("Using the following options: server=\"%s\" port=\"%s\", user=\"%s\", img_url=\"%s\", message=\"%s\"\n", cfg.server, cfg.port, cfg.user, cfg.image, cfg.message);

    /* Check client options for completeness */
    if( cfg.help == 1 || cfg.server == NULL || cfg.port == NULL || cfg.user == NULL || cfg.message == NULL) {
        print_usage();
    } else {
        start_communication();
    }
 
    return 0;
}
